﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: LinearClassifierBase.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2012-12-08 8:26 AM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using BOL.Linq;
using BOL.Linq.Descriptive;
using BOL.Linq.Distances;

namespace BOL.Algorithms.Classification
{
    public abstract class LinearClassifierBase<TClass> : ClassifierBase<double, TClass>, ILinearClassifier<TClass>
    {
        #region Public methods

        public IDictionary<TClass, double[]> Weights { get; private set; }

        public int K { get { return Weights.First().Value.Length - 1; } }

        #endregion

        #region Constructor

        protected LinearClassifierBase(IDictionary<TClass, double[]> weights)
        {
            if (weights == null)
                throw new ArgumentNullException("weights");

            Weights = weights;
        }

        #endregion

        #region Public methods

        public override TClass Classify(double[] features)
        {
            if (features == null)
                throw new ArgumentNullException("features");
            if (features.Length != K)
                throw new ArgumentOutOfRangeException("features");

            return Weights.ToDictionary(x => x.Key, x => x.Value.Dot(features) + x.Value.Last()).ArgMax();
        }

        #endregion

        #region IEquatable<ILinearClassifier<TClass>> implementation

        public bool Equals(ILinearClassifier<TClass> other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            return Weights.DictionaryEqual(other.Weights);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return Weights.OrderBy(x => x.Key).Aggregate(1, (hashCode, x) => hashCode ^ x.Key.GetHashCode());
        }

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is ILinearClassifier<TClass>))
                throw new InvalidCastException("The 'other' argument is not a ILinearClassifier<TClass> object.");

            return Equals(other as ILinearClassifier<TClass>);
        }

        public override string ToString()
        {
            return String.Join("\n", Weights.Select(x => String.Format("y_{0}={1}", x.Key, ToFeatureString(x.Value))));
        }

        private static string ToFeatureString(IList<double> weight)
        {
            var length = weight.Count;

            return String.Concat(String.Concat(Enumerable.Range(0, length - 1).Select(x => weight[x] >= 0 ? String.Format("+{0}x{1}", weight[x], x + 1) : String.Format("{0}x{1}", weight[x], x + 1))), weight[length - 1]);
        }

        #endregion
    }
}